package com.yst.sqlbuilder;

import com.yst.lambda.LambdaFuncUtils;
import com.yst.lambda.TableFieldFunc;
import com.yst.util.BeanUtils;
import com.yst.util.ClassUtils;
import lombok.Data;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class CObject<T> {
    @Data
    static class ChangedValue {
        private Object oldValue;
        private Object newValue;
    }
    public interface IChange<T>{
        void change(CObject<T> obj);
    }
    private final T origin;
    public CObject(T origin){
        this.origin=origin;
    }
    private final Map<String,ChangedValue> changedValueMap= new HashMap<>();
    private boolean modified=false;
    private final AtomicInteger updateCount=new AtomicInteger(0);
    public void updateBegin(){
        updateCount.incrementAndGet();
    }
    public void updateEnd(){
        if (updateCount.decrementAndGet()==0){
            if (!changedValueMap.isEmpty()){
                doChange();
            }
        }
    }
    private final List<IChange<T>> changeWatchList=new ArrayList<>();
    public CObject<T> watchChange(IChange<T> callback){
        if (!changeWatchList.contains(callback)){
            changeWatchList.add(callback);
        }
        return this;
    }
    public void unwatchChange(IChange<T> callback){
        changeWatchList.remove(
                callback
        );
    }
    private void doChange(){
        for(IChange<T> callback:changeWatchList){
            try{
                callback.change(this);
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    private void modify(){
        if (!modified){
            modified=true;
            if (updateCount.get()==0) {
                doChange();
            }
        }
    }
    private synchronized void changeValue(String fieldName,Object value) throws Exception {
        if (!Objects.equals(BeanUtils.getFieldValue(origin, fieldName,null),value)){
            if (!changedValueMap.containsKey(fieldName)){
                ChangedValue changedValue=new ChangedValue();
                changedValue.oldValue = BeanUtils.getFieldValue(origin, fieldName, null);
                changedValueMap.put(fieldName,changedValue);
            }
            ChangedValue changedValue=changedValueMap.get(fieldName);
            changedValue.setNewValue(value);
            BeanUtils.setFieldValue(fieldName,origin,value);
            modify();
        }
    }
    public <R> CObject<T> setValue(TableFieldFunc<T,R> field,R value) throws Exception {
        String fieldName=LambdaFuncUtils.getFieldName(field);
        changeValue(fieldName,value);
        return this;
    }
    public <R> R getValue(TableFieldFunc<T,R> field,R defaultValue){
        String fieldName=LambdaFuncUtils.getFieldName(field);
        if (changedValueMap.containsKey(fieldName)){
            if (Objects.nonNull(changedValueMap.get(fieldName).getNewValue())){
                return (R)changedValueMap.get(fieldName).getNewValue();
            }
            return defaultValue;
        }
        return (R)BeanUtils.getFieldValue(origin,fieldName,defaultValue);
    }
    public <R> R getOldValue(TableFieldFunc<T,R> field){
        String fieldName=LambdaFuncUtils.getFieldName(field);
        if (changedValueMap.containsKey(fieldName)){
            if (Objects.nonNull(changedValueMap.get(fieldName).getOldValue())){
                return (R)changedValueMap.get(fieldName).getOldValue();
            }
            return null;
        }
        return null;
    }
    public CObject<T> submitChange(){
        modified=false;
        updateCount.set(0);
        return this;
    }
    public CObject<T> assign(T entity,String...ignoreFields) throws Exception {
        Map<String, Field> fieldMap= ClassUtils.getClassFields(entity.getClass());
        List<String> ignoreFieldList= Arrays.asList(ignoreFields);
        String fields= Arrays.stream(ignoreFields).collect(Collectors.joining(";",";",";"));
        if (Objects.nonNull(fieldMap)) {
            for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
                String fieldName = entry.getKey();
                String findFieldName=";".concat(fieldName).concat(";");
                if (!fields.contains(findFieldName)){
                    changeValue(fieldName, BeanUtils.getFieldValue(entity, fieldName, null));
                }
            }
        }
        return this;
    }


}
